链表
链表总结
链表是一种由节点(Node)组成的线性数据集合,每个节点通过指针指向下一个节点。它是一种由节点组成,并能用于表示序列的数据结构。
链表在进行循环遍历时效率不高,访问元素O(n)效率低,但是插入和删除时优势明显O(1),且空间复杂度较高。链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
数组的特点是:可以很方便地进行随机访问O(1),但是增删元素O(n)比较耗时
选择数组还是链表要考虑业务对访问改和增删的要求
单链表
一个node只有一个指向下一个节点的指针,最后一个node指向null。一个指向链表头部的头节点。
java实现:
1 | package structures; |
在尾部添加一个元素可以添加一个指向尾节点的指针last,oldlast=last,last=new node(v), oldlast.next=last.
双向链表
每个节点都有一个next指针指向后继,一个prev指针指向前者,能轻易的读取一个极点的前驱和后继,但是有更大的空间开销,在访问遍历等情况下有更高的复杂度,因为有更多的指针操作.
有头尾指针,双向遍历
循环链表
分循环单链表和循环双链表,从表的任何一个节点出发都能找到表中的某个元素,双向循环链表很容易在尾部添加一个元素。
链表相关面试题
1.链表倒置(原地)O(n):
转置需要三个指针pre,head,sus,例如一个1->2->3的链表。第一步转置前两个节点:pre->1,head->2,sus->3,head.next->1, pre.next=null,此时:1<-2 3
第二步循环,若sus!=null, pre->2,head->3,sus->null,head.next->2.此时:1<-2<-3,且sus=null,终止。
1 | public void reverse(){ |
还可以新建一个链表,删除旧链表的元素同时加入新链表。空间复杂度更大一点。
2. 查找单链表的中间节点
采用快慢指针的方式查找单链表的中间节点,快指针一次走两步,慢指针一次走一步,当快指针走完时,慢指针刚好到达中间节点。
3. 查找倒数第k个元素
采用两个指针P1,P2,P1先前移K步,然后P1、P2同时移动,当p1移动到尾部时,P2所指位置的元素即倒数第k个元素 。
4.给定链表的头指针和一个节点指针,在O(1)时间删除该节点。[Google面试题]
分析:本题与《编程之美》上的「从无头单链表中删除节点」类似。主要思想都是「狸猫换太子」,即用下一个节点数据覆盖要删除的节点,然后删除下一个节点。但是如果节点是尾节点时,该方法就行不通了。
1 | node next=cur.next; |
5. 判断单链表是否存在环
题目描述:输入一个单向链表,判断链表是否有环?
分析:通过两个指针,分别从链表的头节点出发,一个每次向后移动一步,另一个移动两步,两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。
栈
栈是先进后出的数据结构LIFO,用数组实现栈的缺点是必须提前声明栈的大小,所以我们用链表来实现栈,可以动态分配栈的大小节约空间。
栈可用于DFS,深度优先遍历
java实现:
1 | package structures; |
用链表来实现栈,以链表头部作为栈顶部,压栈的时候创建一个新节点连接在头部前边。操作都在头部比较简单,没有很多的指针操作。
pop一个空栈抛出 EmptyStackException异常,同样的情况javascript的处理是返回一个undifined, php返回null。
队列
于栈相对,是先进先出FIFO的数据结构,也可以用数组或链表实现。可以用于实现广度遍历BFS。
java实现:
1 | package structures; |
队列用一个带有头指针和尾指针的链表实现,入队列的时候我们从尾部添加元素。出队列时从头指针删除元素。
当删除一个空队列的元素时,抛出NoSuchElementException异常。
栈和队列相关面试题
1.用两个栈实现一个队列
翻译过来就是使用两个栈定义一种先放入的元素,最先被取出的数据结构。
此题应考虑到两种情况,首先最简单的一种情况,假设有 1,2,3,4,5 个元素依次进入自定义的队列,再依次取出。由于是进栈操作都进行完了才进行出栈操作,所以我们只需在元素出队时,将进栈元素倒入另一个空栈中即可。示意图如下:
再一种情况是,如果 add poll 操作是交替进行的,那么如何保证数据结构先进先出的定义呢?比如先放入 1,2,3然后要进行一次取出操作取出 1,随后在进行 add 操作放入4,5,这种情况下如何操作两个栈,才能保证之后再取出的时候元素为 2,3,4,5 顺序?实际上我们只需要保证一下两点就可以:
- 无论如果 StackA(最开始add元素的那个栈) 要往 StackB 中压入元素,那么必须选择一次性全部压入。
- 无论什么时候从队列中取元素,必须保证元素是从 StackB 中 pop 出的,也就是说,当 StackB 不为空的时候绝不能再次向 StackB 中压入元素。
java实现:
1 | package structures; |
2.两个队列实现一个栈
队列不能逆序,需要换一种思路,用两个队列交替输出。
java实现:
1 | package structures; |
3.给定一个栈的输入序列,判断输出序列是否合法
用一个循环来实现,假设输入序列1,2 3,4,5 输出序列4,3,5,1,2。我们依次读取输出序列的值,若在栈顶,就出栈,否则继续压栈直到压入该数字。如果输入已经全部压栈,仍没有栈顶元素等于改元素,则该输入不合法。总之查看每个输出的可能性。
如图:
java实现:
1 | package algos; |
部分参考图片和解释来自于https://juejin.im/post/5b5536825188251af6622815